package fr.nts.framework.database.jpa.dao;

import java.util.Collection;
import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.EntityNotFoundException;
import javax.persistence.PersistenceContext;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;

import org.slf4j.Logger;

import fr.nts.framework.core.commun.tool.TimeUtils;
import fr.nts.framework.database.jpa.model.AJPAModel;

/**
 * Représente un DAO qui utilise le framework J2EE: JPA.
 * Cette classe abstraite pré-définie un certain nombre méthodes utilitaire pour manipuler des entités.
 * 
 * @author sco
 * @version 1.0.0
 */
public abstract class AJPADao<T extends AJPAModel<?>, PK extends Object>{

    protected final static String HIBERNATE_CACHE_HINT = "org.hibernate.cacheable";
      
    @PersistenceContext(unitName = "jpaUnit")
    private EntityManager em;
    
    /**
     * Recherche tous les beans entité du type générique configuré.
     * 
     * @return Une liste d'objets entités du type générique configuré.
     */
    public List<T> findAll(){
    	CriteriaBuilder builder = getEntityManager().getCriteriaBuilder();
    	CriteriaQuery<T> query = builder.createQuery(getBoClass());
    	
    	// Requète de base: select * from T
    	query.from(getBoClass());

    	long start = System.currentTimeMillis();
    	
        List<T> result = getEntityManager().createQuery(query)
			                               .setHint(HIBERNATE_CACHE_HINT, 
			                                		true)
			                               .getResultList();
        
        long fin = System.currentTimeMillis();
        
        if(getLog().isDebugEnabled()){
        	getLog().debug("@@@ Temps écoulé pour récupérer la liste des {} entités {} ==> {}", 
        			       new Object[]{result.size(), getBoClass(), TimeUtils.getTime(start, fin)});
        }
        
        return result;
    }
    
    
    /**
     * Recherche un bean entité du type générique configuré à partir de son identifiant.
     * 
     * @param id L'id de l'objet entité à récupérer.
     * @return L'objet entité initialisé avec les données de la base.
     */
    public T findById(final PK id){
        T bo = null;
        try{
        	long start = System.currentTimeMillis();
        	
            bo = (T)getEntityManager().find(getBoClass(), 
            		                        id);
            
            long fin = System.currentTimeMillis();
            
            if(getLog().isDebugEnabled()){
            	getLog().debug("@@@ Temps écoulé pour récupérer l'entité {} [{}] ==> {}", 
            			       new Object[]{getBoClass(), id, TimeUtils.getTime(start, fin)});
            }
        }catch(EntityNotFoundException e){
			// Entité non trouvé
        	getLog().warn("Impossible de trouver l'entité qui a pour ID: {}", id);
		}
        
        return bo;
    }
 
    
    /**
     * Met à jour un bean entité.
     * Met à jour la base de données en conséquence.
     * 
     * @param bo L'objet entité à modifier.
     */
    public void update(T bo){
    	long start = System.currentTimeMillis();
		
		getEntityManager().merge(bo);
		
        long fin = System.currentTimeMillis();
        
        if(getLog().isDebugEnabled()){
        	getLog().debug("@@@ Temps écoulé pour modifier l'entité {} [{}] ==> {}", 
        			       new Object[]{getBoClass(), bo.getId(), TimeUtils.getTime(start, fin)});
        }
    }
    
    
    /**
     * Met à jour une liste de beans entités.
     * Met à jour la base de données en conséquence.
     * 
     * @param bos La liste des objets entités à modifier.
     */
    public void update(Collection<T> bos){
        for(T bo: bos){
            update(bo);
        }
    }

    
    /**
     * Sauvegarde un bean entité.
     * Met à jour la base de données en conséquence.
     * 
     * @param bo L'objet entité à sauvegarder.
     */
    public void save(T bo){
    	getEntityManager().persist(bo);
    }
    
    
    /**
     * Sauvegarde une liste de beans entités.
     * Met à jour la base de données en conséquence.
     * 
     * @param bos La liste des objets entités à sauvegarder.
     */
    public void save(Collection<T> bos){
        for(T bo: bos){
            save(bo);
        }
    }
    
    
    /**
     * Efface un bean entité.
     * Met à jour la base de données en conséquence.
     * 
     * @param bo L'objet entité à effacer.
     */
    public void delete(T bo){
        try{
        	getEntityManager().remove(bo);
        }catch(EntityNotFoundException e){
			getLog().warn("Impossible de supprimer l'entité passé en parametre: {} ==> {}", bo, e);
        }
    }

    
    /**
     * Efface un bean entité à partir de son identifiant.
     * Met à jour la base de données en conséquence.
     * 
    * @param id L'id de l'objet entité à effacer.
     */
    public void deleteById(final PK id){
        T oldBo = (T)getEntityManager().find(getBoClass(), 
        		                             id);
        
        if(oldBo != null){
        	delete(oldBo);
        }
    }
    
    
    /**
     * Efface une liste de beans entités à partir de leur identifiant.
     * Met à jour la base de données en conséquence.
     * 
     * @param ids La liste des id des objets entité à effacer.
     */
    public void deleteById(final Collection<PK> listId){
        for(PK id: listId){
            deleteById(id);
        }
    }
    

    /**
     * Efface une liste de beans entités.
     * Met à jour la base de données en conséquence.
     * 
     * @param bos La liste des objets entités à effacer.
     */
    public void delete(Collection<T> bos){
        for(T bo: bos){
            delete(bo);
        }
    }

    
    /**
     * Liste tous les objets
     */
    public void print(){
    	List<T> objets = findAll();
    	getLog().debug("On a {} objets pour l'entité de type [{}]", objets.size(), getBoClass());
    	
    	if(getLog().isDebugEnabled()){
	    	for(T obj: objets){
	    		getLog().debug("{}", obj);
	    	}
    	}
    }
    
    
    // GETTER / SETTER
    protected EntityManager getEntityManager(){
    	return em;
    }
    
    protected abstract Class<T> getBoClass();
    
    protected abstract Logger getLog();
}